package asu.logging
{
	/**
	 *  This class provides the basic functionality required by the logging framework 
	 *  for a target implementation.
	 *  It handles the validation of filter expressions and provides a default level 
	 *  property.
	 *  No implementation of the <code>logEvent()</code> method is provided.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 */
	public class AbstractTarget implements ILoggingTarget
	{
		/**
		 *  @private
		 *  Char codes for 0123456789ABCDEF
		 */
		private static const ALPHA_CHAR_CODES:Array = [48, 49, 50, 51, 52, 53, 54, 
			55, 56, 57, 65, 66, 67, 68, 69, 70];
		
		/**
		 *  Generates a UID (unique identifier) based on ActionScript's
		 *  pseudo-random number generator and the current time.
		 *
		 *  <p>The UID has the form
		 *  <code>"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"</code>
		 *  where X is a hexadecimal digit (0-9, A-F).</p>
		 *
		 *  <p>This UID will not be truly globally unique; but it is the best
		 *  we can do without player support for UID generation.</p>
		 *
		 *  @return The newly-generated UID.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 *  @playerversion AIR 1.1
		 *  @productversion Flex 3
		 */
		public static function createUID():String
		{
			var uid:Array = new Array(36);
			var index:int = 0;
			
			var i:int;
			var j:int;
			
			for (i = 0; i < 8; i++)
			{
				uid[index++] = ALPHA_CHAR_CODES[Math.floor(Math.random() *  16)];
			}
			
			for (i = 0; i < 3; i++)
			{
				uid[index++] = 45; // charCode for "-"
				
				for (j = 0; j < 4; j++)
				{
					uid[index++] = ALPHA_CHAR_CODES[Math.floor(Math.random() *  16)];
				}
			}
			
			uid[index++] = 45; // charCode for "-"
			
			var time:Number = new Date().getTime();
			// Note: time is the number of milliseconds since 1970,
			// which is currently more than one trillion.
			// We use the low 8 hex digits of this number in the UID.
			// Just in case the system clock has been reset to
			// Jan 1-4, 1970 (in which case this number could have only
			// 1-7 hex digits), we pad on the left with 7 zeros
			// before taking the low digits.
			var timeString:String = ("0000000" + time.toString(16).toUpperCase()).substr(-8);
			
			for (i = 0; i < 8; i++)
			{
				uid[index++] = timeString.charCodeAt(i);
			}
			
			for (i = 0; i < 4; i++)
			{
				uid[index++] = ALPHA_CHAR_CODES[Math.floor(Math.random() *  16)];
			}
			
			return String.fromCharCode.apply(null, uid);
		}
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  Constructor.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function AbstractTarget()
		{
			super();
			_id = createUID();
		}
		
		//--------------------------------------------------------------------------
		//
		//  Variables
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  @private
		 *  Count of the number of loggers this target is listening to. When this
		 *  value is zero changes to the filters property shouldn't do anything
		 */
		private var _loggerCount:uint = 0;
		
		//--------------------------------------------------------------------------
		//
		//  Properties
		//
		//--------------------------------------------------------------------------
		
		//----------------------------------
		//  filters
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the filters property.
		 */
		private var _filters:Array = [ "*" ];
	
		/**
		 *  In addition to the <code>level</code> setting, filters are used to
		 *  provide a psuedo-hierarchical mapping for processing only those events
		 *  for a given category.
		 *  <p>
		 *  Each logger belongs to a category.
		 *  By convention these categories map to the fully-qualified class name in
		 *  which the logger is used.
		 *  For example, a logger that is logging messages for the
		 *  <code>mx.rpc.soap.WebService</code> class, uses 
		 *  "mx.rpc.soap.WebService" as the parameter to the 
		 *  <code>Log.getLogger()</code> method call.
		 *  When messages are sent under this category only those targets that have
		 *  a filter which matches that category receive notification of those
		 *  events.
		 *  Filter expressions can include a wildcard match, indicated with an
		 *  asterisk.
		 *  The wildcard must be the right-most character in the expression.
		 *  For example: rpc~~, mx.~~, or ~~.
		 *  If an invalid expression is specified, a <code>InvalidFilterError</code>
		 *  is thrown.
		 *  If <code>null</code> or [] is specified, the filters are set to the
		 *  default of ["~~"].
		 *  </p>
		 *  <p>For example:
		 *     <pre>
		 *           var traceLogger:ILoggingTarget = new TraceTarget();
		 *           traceLogger.filters = ["mx.rpc.~~", "mx.messaging.~~"];
		 *           Log.addTarget(traceLogger);
		 *     </pre>
		 *  </p>
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function get filters():Array
		{
			return _filters;
		}
		
		/**
		 *  @private
		 *  This method will make sure that all of the filter expressions specified
		 *  are valid, and will throw <code>InvalidFilterError</code> if any are not.
		 */
		public function set filters(value:Array):void
		{
			if (value && value.length > 0)
			{
				// a valid filter value will be fully qualified or have a wildcard
				// in it.  the wild card can only be located at the end of the
				// expression.  valid examples  xx*, xx.*,  *
				var filter:String;
				var index:int;
				var message:String;
				for (var i:uint = 0; i<value.length; i++)
				{
					filter = value[i];
					// check for invalid characters
					/*if (Log.hasIllegalCharacters(filter))
					{
						message = resourceManager.getString(
							"logging", "charsInvalid", [ filter ]);
						throw new InvalidFilterError(message);
					}*/
					
					index = filter.indexOf("*");
					/*if ((index >= 0) && (index != (filter.length -1)))
					{
						message = resourceManager.getString(
							"logging", "charPlacement", [ filter ]);
						throw new InvalidFilterError(message);
					}*/
				} // for
			}
			else
			{
				// if null was specified then default to all
				value = ["*"];
			}
			
			if (_loggerCount > 0)
			{
				Log.removeTarget(this);
				_filters = value;
				Log.addTarget(this);
			}
			else
			{
				_filters = value;
			}
		}
		
		//----------------------------------
		//  id
		//----------------------------------
		
		/**
		 *  @prviate
		 *  Storage for the id property.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 *  @playerversion AIR 1.1
		 *  @productversion Flex 3
		 */
		private var _id:String;
		
		/**
		 *  Provides access to the id of this target.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function get id():String
		{
			return _id;
		}
		
		//----------------------------------
		//  level
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the level property.
		 */
		private var _level:int = LogEventLevel.ALL;
		
		/**
		 *  Provides access to the level this target is currently set at.
		 *  Value values are:
		 *    <ul>
		 *      <li><code>LogEventLevel.FATAL (1000)</code> designates events that are very
		 *      harmful and will eventually lead to application failure</li>
		 *
		 *      <li><code>LogEventLevel.ERROR (8)</code> designates error events that might
		 *      still allow the application to continue running.</li>
		 *
		 *      <li><code>LogEventLevel.WARN (6)</code> designates events that could be
		 *      harmful to the application operation</li>
		 *
		 *      <li><code>LogEventLevel.INFO (4)</code> designates informational messages
		 *      that highlight the progress of the application at
		 *      coarse-grained level.</li>
		 *
		 *      <li><code>LogEventLevel.DEBUG (2)</code> designates informational
		 *      level messages that are fine grained and most helpful when
		 *      debugging an application.</li>
		 *
		 *      <li><code>LogEventLevel.ALL (0)</code> intended to force a target to
		 *      process all messages.</li>
		 *    </ul>
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function get level():int
		{
			return _level;
		}
		
		/**
		 *  @private
		 */
		public function set level(value:int):void
		{
			// A change of level may impact the target level for Log.
			Log.removeTarget(this);
			_level = value;
			Log.addTarget(this);        
		}
		
		//--------------------------------------------------------------------------
		//
		//  Methods
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  Sets up this target with the specified logger.
		 *  This allows this target to receive log events from the specified logger.
		 *
		 *  @param logger The ILogger that this target should listen to.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function addLogger(logger:ILogger):void
		{
			if (logger)
			{
				_loggerCount++;
				logger.addEventListener(LogEvent.LOG, logHandler);
			}
		}
		
		/**
		 *  Stops this target from receiving events from the specified logger.
		 *
		 *  @param logger The ILogger that this target should ignore.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function removeLogger(logger:ILogger):void
		{
			if (logger)
			{
				_loggerCount--;
				logger.removeEventListener(LogEvent.LOG, logHandler);
			}
		}
		
		/**
		 *  Called after the implementing object has been created
		 *  and all properties specified on the tag have been assigned.
		 *
		 *  @param document MXML document that created this object.
		 *
		 *  @param id Used by the document to refer to this object.
		 *  If the object is a deep property on the document, id is null.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function initialized(document:Object, id:String):void
		{
			_id = id;
			Log.addTarget(this);
		}
		
		/**
		 *  This method handles a <code>LogEvent</code> from an associated logger.
		 *  A target uses this method to translate the event into the appropriate
		 *  format for transmission, storage, or display.
		 *  This method will be called only if the event's level is in range of the
		 *  target's level.
		 *
		 *  <p><b><i>NOTE: Descendants must override this method to make it useful.</i></b></p>
		 *  
		 *  @param event An event from an associated logger.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 */
		public function logEvent(event:LogEvent):void
		{
		}
		
		//--------------------------------------------------------------------------
		//
		//  Event handlers
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  @private
		 *  This method will call the <code>logEvent</code> method if the level of the
		 *  event is appropriate for the current level.
		 */
		private function logHandler(event:LogEvent):void
		{
			if (event.level >= level)
				logEvent(event);
		}
	}
}